import { HttpException, HttpStatus, Injectable } from '@nestjs/common'
import { CreateRoleDto } from './dto/create-role.dto'
import { UpdateRoleDto } from './dto/update-role.dto'
import { Role } from './entities/role.entity'
import { InjectRepository } from '@nestjs/typeorm'
import { QueryFailedError, Repository } from 'typeorm'
import { HttpExceptionEnum } from 'types/http-exception'
import { Menu, RoleMenu } from '../menu/entities/menu.entity'
import { User } from 'src/user/entities/user.entity'
import { generateSelect, hasPermission } from 'src/utils'
import { UserFromRequest } from 'types/global'

const roleSelect = generateSelect(Role)
@Injectable()
export class RoleService {
  constructor(
    @InjectRepository(Role) private readonly roleRepository: Repository<Role>,
    @InjectRepository(RoleMenu)
    private readonly roleMenuRepository: Repository<RoleMenu>
  ) {}

  async create(createRoleDto: CreateRoleDto, userId: number) {
    try {
      const role = new Role({
        ...createRoleDto,
        createdBy: userId,
        updatedBy: userId
      })
      return await this.roleRepository.save(role)
    } catch (error) {
      console.log('createRole---------创建角色失败')
      console.log(error)

      if (error instanceof QueryFailedError) {
        throw new HttpException(
          HttpExceptionEnum.ROLE_EXISTED,
          HttpStatus.BAD_REQUEST
        )
      }
      throw error
    }
  }

  /**
   * @description 查询角色列表
   * @param params params 分页参数信息
   * @param userId 传入userId，查询自己创建的角色
   * @returns
   */
  async findAll(
    params: {
      keyword?: string
      pageNum?: number
      pageSize?: number
    } = {},
    userId?: number
  ) {
    const { keyword = '', pageNum = 1, pageSize = 10 } = params
    const skip = (pageNum - 1) * pageSize
    const queryBuilder = this.roleRepository.createQueryBuilder('role')
    if (keyword) {
      queryBuilder.where('role.name LIKE :keyword OR role.code LIKE :keyword', {
        keyword: `%${keyword}%`
      })
    }

    if (userId) {
      queryBuilder.andWhere('role.createdBy = :userId', { userId })
    }

    queryBuilder.orderBy('role.createTime', 'DESC')
    queryBuilder
      .leftJoinAndSelect('role.createdBy', 'createdBy')
      .leftJoinAndSelect('role.updatedBy', 'updatedBy')

    const [list, total] = await Promise.all([
      queryBuilder.skip(skip).take(pageSize).select(roleSelect).getRawMany(),
      queryBuilder.getCount()
    ])

    return {
      list,
      total,
      pageNum,
      pageSize
    }
  }

  async findOne(id: number) {
    const queryBuilder = this.roleRepository
      .createQueryBuilder('role')
      .where('role.id = :id', { id })
      .select(roleSelect)
      .leftJoinAndSelect('role.createdBy', 'createdBy')
      .leftJoinAndSelect('role.updatedBy', 'updatedBy')
    const role = await queryBuilder.getRawOne()
    if (!role)
      throw new HttpException(
        HttpExceptionEnum.ROLE_NOT_EXIST,
        HttpStatus.BAD_REQUEST
      )
    return role
  }

  async update(updateRoleDto: UpdateRoleDto, user: UserFromRequest) {
    try {
      const role = await this.findOne(updateRoleDto.id)
      if (!role)
        throw new HttpException(
          HttpExceptionEnum.ROLE_NOT_EXIST,
          HttpStatus.BAD_REQUEST
        )
      hasPermission(user, role.createdBy)
      Object.assign(role, updateRoleDto, { updatedBy: user?.userId })
      const newRole = await this.roleRepository.save(role)

      return newRole
    } catch (error) {
      if (error instanceof QueryFailedError) {
        throw new HttpException(
          HttpExceptionEnum.ROLE_EXISTED,
          HttpStatus.BAD_REQUEST
        )
      }
      throw error
    }
  }

  /**
   * @description 删除角色
   * @param {number}  id
   * @returns
   */
  async remove(id: number, user: UserFromRequest) {
    try {
      // 1. 从数据库中查找角色
      const role = await this.findOne(id)
      if (!role) {
        throw new HttpException(
          HttpExceptionEnum.USER_NOT_EXIST,
          HttpStatus.BAD_REQUEST
        )
      }

      hasPermission(user, role.createdBy)
      // 2. 删除或更新 user 表中所有引用该角色的记录
      await this.roleRepository
        .createQueryBuilder()
        .update(User)
        .set({ roleId: null })
        .where('roleId = :id', { id })
        .execute()

      // 3. 删除角色
      await this.roleRepository.remove(role)

      return `Role with id ${id} has been removed`
    } catch (error) {
      console.log(error)
      throw error
    }
  }

  transferMenuToTree(menuList: Menu[], roleMenuIds: any[]) {
    // 将 roleMenuIds 转换为 Set 以便快速查找
    const roleMenuIdSet = new Set(
      roleMenuIds.map((roleMenu) => roleMenu.menuId)
    )

    // 构建树结构
    const menuMap: { [key: number]: any } = {}
    const tree: any[] = []

    menuList.forEach((menu: any) => {
      menuMap[menu.id] = { ...menu, children: [] }
    })

    menuList.forEach((menu: any) => {
      if (menu.parentId !== null && menuMap[menu.parentId]) {
        menuMap[menu.parentId].children.push(menuMap[menu.id])
      } else {
        tree.push(menuMap[menu.id])
      }
    })

    // 递归函数检查子节点
    const allChildrenChecked = (menu: any): boolean => {
      if (!menu.children || menu.children.length === 0) {
        return roleMenuIdSet.has(menu.id)
      }
      return menu.children.every((child: any) => allChildrenChecked(child))
    }

    // 遍历树结构并设置 checked 属性
    const traverseAndSetChecked = (menus: any[]) => {
      menus.forEach((menu: any) => {
        if (allChildrenChecked(menu)) {
          menu.checked = true
        } else {
          menu.checked = false
        }
        traverseAndSetChecked(menu.children)
      })
    }

    traverseAndSetChecked(tree)
    return tree
  }

  /**
   * @description 绑定角色的菜单id
   * @param {number} roleId
   * @param {number[]} menuIds
   */
  async bindRoleAuthMenu(roleId: number, menuIds: number[]) {
    // 查找现有角色菜单
    const existingRoleMenus = await this.roleMenuRepository.find({
      where: { roleId }
    })

    // 创建一个 Set 来存储现有菜单 ID，以便快速查找
    const existingMenuIdsSet = new Set(
      existingRoleMenus.map((roleMenu) => roleMenu.menuId)
    )

    // 更新现有角色菜单的状态
    const updatedRoleMenus = existingRoleMenus.map((roleMenu) => {
      if (menuIds.includes(roleMenu.menuId)) {
        roleMenu.status = 1 // 启用
      } else {
        roleMenu.status = 0 // 禁用
      }
      return roleMenu
    })

    // 找出需要新增的角色菜单
    const newMenuIds = menuIds.filter(
      (menuId) => !existingMenuIdsSet.has(menuId)
    )
    const newRoleMenus = newMenuIds.map((menuId) => ({
      roleId,
      menuId,
      status: 1 // 新增的菜单默认启用
    }))

    // 保存更新的角色菜单
    await this.roleMenuRepository.save(updatedRoleMenus)

    // 插入新的角色菜单
    if (newRoleMenus.length > 0) {
      await this.roleMenuRepository.insert(newRoleMenus)
    }
  }

  /**
   * @description 角色下菜单分配操作权限
   * @param {number} roleId
   * @param {string[]} operationCodes
   * @param {number} menuId
   */
  async bindRoleAuthOperation(roleMenuDto: {
    roleId: number
    menuId: number
    operationCodes: string[]
  }) {
    const operations = roleMenuDto.operationCodes.join(',')
    const roleMenu = new RoleMenu({ ...roleMenuDto, operations })

    return await this.roleMenuRepository.save(roleMenu)
  }

  /**
   * @description 角色下菜单分配接口权限
   * @param {number} roleId
   * @param {string[]} permissions
   * @param {number} menuId
   */
  async bindRoleAuthPermission(roleMenuDto: {
    roleId: number
    menuId: number
    permissions: string[]
  }) {
    const permissions = roleMenuDto.permissions.join(',')
    const roleMenu = new RoleMenu({ ...roleMenuDto, permissions })

    return await this.roleMenuRepository.save(roleMenu)
  }

  /**
   * @description 查询单个角色下的权限相关内容
   * @param roleId 角色Id
   * @returns {Promise<Array<string>>} 所有接口相关的权限
   */
  async getPermission(roleId: number) {
    const queryBuilder = this.roleMenuRepository
      .createQueryBuilder('roleMenu')
      .where('roleMenu.role_id = :roleId', { roleId })
      .andWhere('roleMenu.status = 1')
    // 获取角色菜单关联信息，包括权限字段
    const roleMenus = await queryBuilder
      .select([
        'roleMenu.role_id AS roleId',
        'roleMenu.menu_id AS menuId',
        'roleMenu.operations AS operations',
        'roleMenu.permissions AS permissions'
      ])
      .getRawMany()

    // 提取并处理权限数据
    const permissions: string[] = []

    roleMenus.forEach((roleMenu) => {
      // 如果状态为启用(1)，则处理权限
      // 处理 permissions 字段
      if (roleMenu.permissions) {
        const permissionList = roleMenu.permissions.split(',')
        permissions.push(...permissionList)
      }
    })
    // 去重并返回权限数组
    return [...new Set(permissions)]
  }
}
